/* Vecmath.

   Copyright (C) 2001, 2002, 2003 Stefan Maierhofer.

   Written by Stefan Maierhofer <sm@cg.tuwien.ac.at>

   This file is part of Vecmath.

   Vecmath is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   Vecmath is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Vecmath; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */

using System;
using NUnit.Framework;

namespace Vecmath
{
    /// <summary>
    /// Box3D.
    /// </summary>
    public struct Box3D
    {

        public static Box3D Empty = new Box3D(Vecmath.HUGE_PNT3D, -Vecmath.HUGE_PNT3D);

        public Box3D(Pnt3D lower, Pnt3D upper)
        {
            this.lower = lower;
            this.upper = upper;
        }

        public Pnt3D Lower
        {
            get { return lower; }
            set { lower = value; }
        }

        public Pnt3D Upper
        {
            get { return upper; }
            set { upper = value; }
        }

        public Pnt3D LLL { get { return new Pnt3D(lower._x[0], lower._x[1], lower._x[2]); } }
        public Pnt3D LLU { get { return new Pnt3D(lower._x[0], lower._x[1], upper._x[2]); } }
        public Pnt3D LUL { get { return new Pnt3D(lower._x[0], upper._x[1], lower._x[2]); } }
        public Pnt3D LUU { get { return new Pnt3D(lower._x[0], upper._x[1], upper._x[2]); } }
        public Pnt3D ULL { get { return new Pnt3D(upper._x[0], lower._x[1], lower._x[2]); } }
        public Pnt3D ULU { get { return new Pnt3D(upper._x[0], lower._x[1], upper._x[2]); } }
        public Pnt3D UUL { get { return new Pnt3D(upper._x[0], upper._x[1], lower._x[2]); } }
        public Pnt3D UUU { get { return new Pnt3D(upper._x[0], upper._x[1], upper._x[2]); } }

        public int MinDim
        {
            get
            {
                Vec3D dim = upper - lower;
                return (dim[0] < dim[1])
                    ? ((dim[0] < dim[2]) ? 0 : 2)
                    : ((dim[1] < dim[2]) ? 1 : 2);
            }
        }

        public int MaxDim
        {
            get
            {
                Vec3D dim = upper - lower;
                return (dim[0] > dim[1])
                    ? ((dim[0] > dim[2]) ? 0 : 2)
                    : ((dim[1] > dim[2]) ? 1 : 2);
            }
        }

        public double Minimum
        {
            get
            {
                Vec3D dim = upper - lower;
                return (dim[0] < dim[1])
                    ? ((dim[0] < dim[2]) ? dim[0] : dim[2])
                    : ((dim[1] < dim[2]) ? dim[1] : dim[2]);
            }
        }

        public double Maximum
        {
            get
            {
                Vec3D dim = upper - lower;
                return (dim[0] > dim[1])
                    ? ((dim[0] > dim[2]) ? dim[0] : dim[2])
                    : ((dim[1] > dim[2]) ? dim[1] : dim[2]);
            }
        }

        public bool IsInside(Pnt3D p)
        {
            return lower < p && upper > p;
        }

        public bool IsInsideOrOnBorder(Pnt3D p)
        {
            return lower <= p && upper >= p;
        }

        public bool IsOnBorder(Pnt3D p)
        {
            return IsInsideOrOnBorder(p) && !IsInside(p);
        }

        public bool IsInside(Box3D b)
        {
            return lower < b.lower && upper > b.upper;
        }

        public bool IsInsideOrOnBorder(Box3D b)
        {
            return lower <= b.lower && upper >= b.upper;
        }

        public bool IsOnBorder(Box3D b)
        {
            return IsInsideOrOnBorder(b) && !IsInside(b);
        }

        public int Relation(int dim, double val)
        {
            if (upper[dim] < val) return -1;
            if (lower[dim] > val) return 1;
            return 0;
        }

        public bool Intersect(Ray3D r)
        {
            bool inside = true;
            int [] quadrant = new int[3];
            int i;
            int whichPlane;
            double [] maxT = new double[3];
            double [] candidatePlane = new double[3];
            Vec3D coord = new Vec3D(0,0,0);

            for (i = 0; i < 3; i++)
            {
                if (r.Point[i] < lower[i]) 
                {
                    quadrant[i] = 1;
                    candidatePlane[i] = lower[i];
                    inside = false;
                }
                else if (r.Point[i] > upper[i]) 
                {
                    quadrant[i] = 0;
                    candidatePlane[i] = upper[i];
                    inside = false;
                }
                else	
                {
                    quadrant[i] = 2;
                }
            }

            if (inside) return true;

            for (i = 0; i < 3; i++)
            {
                if (quadrant[i] != 2 && r.Direction[i] != 0.0)
                {
                    maxT[i] = (candidatePlane[i]-r.Point[i]) / r.Direction[i];
                }
                else
                {
                    maxT[i] = -1.0;
                }
            }

            whichPlane = 0;
            for (i = 1; i < 3; i++)
            {
                if (maxT[whichPlane] < maxT[i]) whichPlane = i;
            }

            if (maxT[whichPlane] < 0.0) return false;
            for (i = 0; i < 3; i++)
            {
                if (whichPlane != i) 
                {
                    coord[i] = r.Point[i] + maxT[whichPlane] * r.Direction[i];
                    if (coord[i] < lower[i] || coord[i] > upper[i])
                    {
                        return false;
                    }
                } 
                else 
                {
                    coord[i] = candidatePlane[i];
                }
            }

            return true;
        }

        public static Box3D operator+(Box3D b, Pnt3D p)
        {
            return new Box3D(Pnt3D.Min(b.lower, p), Pnt3D.Max(b.upper, p));
        }

        public static Box3D operator+(Box3D b, Box3D c)
        {
            return new Box3D(Pnt3D.Min(b.lower, c.lower), Pnt3D.Max(b.upper, c.upper));
        }

        public static bool ApproxEqual(Box3D a, Box3D b)
        {
            return
                Vecmath.ApproxEquals(a.Lower, b.Lower) &&
                Vecmath.ApproxEquals(a.Upper, b.Upper);
        }

        public override bool Equals(object obj)
        {
            Box3D x = (Box3D)obj;
            return (lower == x.Lower && upper == x.Upper);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode ();
        }

        public override string ToString()
        {
            return "Box3D(" + lower + ", " + upper + ")";
        }

        internal Pnt3D lower;
        internal Pnt3D upper;


    }


    [TestFixture]
    public class Box3DTest
    {
        [Test]
        public void InitTest()
        {
            Box3D b = new Box3D(new Pnt3D(1,2,3), new Pnt3D(4,5,6));
            Assert.AreEqual(new Pnt3D(1,2,3), b.Lower);
            Assert.AreEqual(new Pnt3D(4,5,6), b.Upper);

            Assert.AreEqual(new Pnt3D(1,2,3), b.LLL);
            Assert.AreEqual(new Pnt3D(1,2,6), b.LLU);
            Assert.AreEqual(new Pnt3D(1,5,3), b.LUL);
            Assert.AreEqual(new Pnt3D(1,5,6), b.LUU);
            Assert.AreEqual(new Pnt3D(4,2,3), b.ULL);
            Assert.AreEqual(new Pnt3D(4,2,6), b.ULU);
            Assert.AreEqual(new Pnt3D(4,5,3), b.UUL);
            Assert.AreEqual(new Pnt3D(4,5,6), b.UUU);
        }

        
    }

}
